home *** CD-ROM | disk | FTP | other *** search
- /*
- * paste - a recreation of the Unix(Tm) paste(1) command.
- *
- * syntax: paste file1 file2 ...
- * paste -dLIST file1 file2 ...
- * paste -s [-dLIST] file1 file2 ...
- *
- * Copyright (C) 1984 by David M. Ihnat
- *
- * This program is a total rewrite of the Bell Laboratories Unix(Tm)
- * command of the same name, as of System V. It contains no proprietary
- * code, and therefore may be used without violation of any proprietary
- * agreements whatsoever. However, you will notice that the program is
- * copyrighted by me. This is to assure the program does *not* fall
- * into the public domain. Thus, I may specify just what I am now:
- * This program may be freely copied and distributed, provided this notice
- * remains; it may not be sold for profit without express written consent of
- * the author.
- * Please note that I recreated the behavior of the Unix(Tm) 'paste' command
- * as faithfully as possible, with minor exceptions (noted below); however,
- * I haven't run a full set of regression * tests. Thus, the user of
- * this program accepts full responsibility for any effects or loss;
- * in particular, the author is not responsible for any losses,
- * explicit or incidental, that may be incurred through use of this program.
- *
- * The changes to the program, with one exception, are transparent to
- * a user familiar with the Unix command of the same name. These changes
- * are:
- *
- * 1) The '-s' option had a bug in the Unix version when used with multiple
- * files. (It would repeat each file in a list, i.e., for
- * 'paste -s file1 file2 file3', it would list
- * <file1\n><file1\n><file2\n><file1\n><file2\n><file3\n>
- * I fixed this, and reported the bug to the providers of the command in
- * Unix.
- *
- * 2) The list of valid escape sequences has been expanded to include
- * \b,\f,\r, and \v. (Just because *I* can't imagine why you'd want
- * to use them doesn't mean I should keep them from you.)
- *
- * 3) There is no longer any restriction on line length.
- *
- * I ask that any bugs (and, if possible, fixes) be reported to me when
- * possible. -David Ihnat (312) 784-4544 ihuxx!ignatz
- */
-
- #include <stdio.h>
-
- extern int errno;
-
- #define CPM
-
- /* I'd love to use enums, but not everyone has them. Portability, y'know. */
- #define NODELIM 1
- #define USAGE 2
- #define BADFILE 3
- #define TOOMANY 4
-
- #define TAB '\t'
- #define NL '\n'
- #define BS '\b'
- #define FF '\f'
- #define CR '\r'
- #define VT '\v'
- #define DEL '\177'
-
- #define _MAXSZ 512
- #define _MAXFILES 12
- #define CLOSED ((FILE *)-1)
- #define ENDLIST ((FILE *)-2)
- #define COMMAND "paste"
-
- char *cmdnam;
-
- short int dflag,sflag;
- char delims[] = { TAB };
-
- main(argc,argv)
- int argc;
- char **argv;
- {
- char *strcpy();
-
- dflag = sflag = 0;
-
- #ifdef CPM
- cmdnam = COMMAND;
- #else
- cmdnam = *argv;
- #endif
-
- /* Skip invocation name */
- argv++;
- argc--;
-
- /* First, parse input options */
-
- while(argv[0][0] == '-' && argv[0][1] != '\0')
- {
- switch(argv[0][1])
- {
- case 'd':
- case 'D':
- /* Delimiter character(s) */
- strcpy(delims,&argv[0][2]);
- if(*delims == '\0')
- prerr(NODELIM,"");
- else
- delimbuild(delims);
-
- break;
-
- case 's':
- case 'S':
- sflag++;
- break;
-
- default:
- prerr(USAGE,"");
- }
- argv++;
- argc--;
- }
-
- /*
- * If no files specified, simply exit. Otherwise,
- * if not the old '-s' option, process all files.
- * If '-s', then process files one-at-a-time.
- */
- if(!sflag)
- docol(argc,argv); /* Column paste */
- else
- doserial(argc,argv); /* Serial paste */
-
- }
-
- docol(nfiles,fnamptr)
- int nfiles;
- char **fnamptr;
- {
- char iobuff[_MAXSZ]; /* i/o buffer for the fgets */
- short int somedone; /* flag for blank field handling */
-
- /*
- * There is a strange case where all files are just ready to
- * be closed, or will on this round. In that case, the string
- * of delimiters must be preserved. delbuf[1] ->delbuf[MAXFILES+1]
- * provides intermediate storage for closed files, if needed;
- * delbuf[0] is the current index.
- */
- char delbuf[_MAXFILES+2];
-
- FILE *fileptr[_MAXFILES+1];
- FILE *fopen();
-
- char *fgets();
-
- int filecnt; /* Set to number of files to process */
- register char *delimptr; /* Cycling delimiter pointer */
- int index; /* Working variable */
- int strend; /* End of string in buffer */
-
- /*
- * Perform column paste. First, attempt to open all
- * files. (This could be expanded to an infinite number of files,
- * but at the (considerable) expense of remembering the file and
- * its current offset, then opening/reading/closing. The
- * commands' utility doesn't warrant the effort; at least, to me...)
- */
-
- for(filecnt=0;(nfiles>0) && (filecnt<_MAXFILES);filecnt++,nfiles--,fnamptr++)
- {
- if(fnamptr[0][0] == '-')
- fileptr[filecnt] = stdin;
- else
- {
- fileptr[filecnt] = fopen(*fnamptr,"r");
- if(fileptr[filecnt] == NULL)
- prerr(BADFILE,*fnamptr);
- }
- }
-
- fileptr[filecnt] = ENDLIST; /* End of list. */
-
- if(nfiles)
- prerr(TOOMANY,"");
-
- /*
- * Have all files. Now, read a line from each file, and output
- * to stdout. Notice that the old 511 character limitation on
- * the line length no longer applies, since this program doesn't
- * do the buffering. Do this until you go through the loop and
- * don't successfully read from any of the files.
- */
- for(;filecnt;)
- {
- somedone = 0; /* Blank field handling flag */
- delimptr = delims; /* Start at beginning of delim list */
- delbuf[0] = 0; /* No squirreled delims */
-
- for(index = 0;(fileptr[index] != ENDLIST) && filecnt; index++)
- {
- /* Read a line and immediately output.
- * If it's too big for the buffer, then dump what was
- * read and go back for more.
- *
- * Otherwise, if it is from the last file, then leave
- * the carriage return in place; if not, replace with
- * a delimiter (if any)
- */
-
- strend = 0; /* Set so can easily detect EOF */
-
- if(fileptr[index] != CLOSED)
-
- while(fgets(iobuff,(_MAXSZ-1),fileptr[index]) != (char *)NULL)
- {
- strend = strlen(iobuff); /* Did the buffer fill? */
-
- if(strend == (_MAXSZ-1))
- {
- /* Gosh, what a long line. */
- fputs(iobuff,stdout);
- strend = 0;
- continue;
- }
-
- /* Ok got whole line in buffer. */
- break; /* Out of loop for this file */
- }
-
- /* Ended either on an EOF (well, actually NULL return--
- * it *could* be some sort of file error, but
- * but if the file was opened successfully, this is
- * unlikely. Besides, error checking on streams
- * doesn't allow us to decide exactly what went
- * wrong, so I'm going to be very Unix-like and ignore
- * it!), or a closed file, or a received line.
- * If an EOF, close the file and mark it in the
- * list. In any case, output the delimiter of choice.
- */
-
- if(!strend)
- {
- if(fileptr[index] != CLOSED)
- {
- fclose(fileptr[index]);
- fileptr[index] = CLOSED;
- filecnt--;
- }
-
- /* Is this the end of the whole thing? */
- if((fileptr[index+1] == ENDLIST) && !somedone)
- continue; /* EXITS */
-
- /* Ok, some files not closed this line. Last file? */
- if(fileptr[index+1] == ENDLIST)
- {
- if(delbuf[0])
- {
- fputs(&delbuf[1],stdout);
- delbuf[0] = 0;
- }
- putc((int)NL,stdout);
- continue; /* Next read of files */
- }else
- {
- /* Closed file; setup delim */
- if(*delimptr != DEL)
- {
- delbuf[0]++;
- delbuf[delbuf[0]] = *delimptr++;
- delbuf[delbuf[0]+1] = '\0';
- }else
- delimptr++;
- }
-
- /* Reset end of delimiter string if necessary */
- if(*delimptr == '\0')
- delimptr = delims;
- }else
- {
- /* Some data read. */
- somedone++;
-
- /* Any saved delims? */
- if(delbuf[0])
- {
- fputs(&delbuf[1],stdout);
- delbuf[0] = 0;
- }
-
- /* If last file, last char will be NL. */
- if(fileptr[index+1] != ENDLIST)
- {
- if(*delimptr == DEL)
- {
- delimptr++;
- iobuff[strend-1] = '\0'; /* No delim */
- }else
- iobuff[strend-1] = *delimptr++;
- }
-
- if(*delimptr == '\0')
- delimptr = delims;
-
- /* Now dump the buffer */
- fputs(iobuff,stdout);
- }
- }
- }
- }
-
- doserial(nfiles,fnamptr)
- int nfiles;
- char **fnamptr;
- {
- /*
- * Do serial paste. Simply scarf characters, performing
- * one-character buffering to facilitate delim processing.
- */
-
- register int charnew,charold;
- register char *delimptr;
-
- register FILE *fileptr;
- FILE *fopen();
-
- for(;nfiles;nfiles--,fnamptr++)
- {
- if(fnamptr[0][0] == '-')
- fileptr = stdin;
- else
- {
- fileptr = fopen(*fnamptr,"r");
-
- if(fileptr == NULL)
- prerr(BADFILE,*fnamptr);
- }
-
- /*
- * The file is open; just keep taking characters,
- * stashing them in charnew; output charold, converting to
- * the appropriate delimiter character if needful. After the
- * EOF, simply output 'charold' if it's a newline; otherwise,
- * output it and then a newline.
- */
-
- delimptr = delims; /* Set up for delimiter string */
-
- if((charold = getc(fileptr)) == EOF)
- {
- /* Empty file! */
- putc(NL,stdout);
- continue; /* Go on to the next file */
- }
-
- /* Ok, 'charold' is set up. Hit it! */
-
- while((charnew = getc(fileptr)) != EOF)
- {
- /* Ok, process the old character */
- if(charold == NL)
- {
- if(*delimptr != DEL)
- putc(*delimptr++,stdout);
-
- /* Reset pointer at end of delimiter string */
- if(*delimptr == '\0')
- delimptr = delims;
- } else
- putc((char)charold,stdout);
-
- charold = charnew;
- }
-
- /* Ok, hit EOF. Process that last character */
-
- putc((char)charold,stdout);
-
- if((char)charold != NL)
- putc(NL,stdout);
- }
- }
-
- delimbuild(strptr)
- char *strptr;
- {
- /*
- * Process the delimiter string into something that can be
- * used by the routines. This involves, primarily, collapsing
- * the backslash representations of special characters into
- * their actual values, and terminating the string in a manner
- * that the routines can recognize. The set of possible backslash
- * characters has been expanded beyond that recognized by the
- * vanilla Unix(Tm) version.
- */
- register char *strout;
-
- strout = strptr; /* Start at the same place, anyway */
-
- while(*strptr)
- {
- if(*strptr != '\\') /* Is it an escape character? */
- *strout++ = *strptr++; /* No, just transfer it */
- else
- {
- strptr++; /* Get past escape character */
- switch(*strptr)
- {
- case '0':
- *strout++ = DEL;
- break;
-
- case 't':
- #ifdef CPM
- case 'T':
- #endif
- *strout++ = TAB;
- break;
-
- case 'n':
- #ifdef CPM
- case 'N':
- #endif
- *strout++ = NL;
- break;
-
- case 'b':
- #ifdef CPM
- case 'B':
- #endif
- *strout++ = BS;
- break;
-
- case 'f':
- #ifdef CPM
- case 'F':
- #endif
- *strout++ = FF;
- break;
-
- case 'r':
- #ifdef CPM
- case 'R':
- #endif
- *strout++ = CR;
- break;
-
- case 'v':
- #ifdef CPM
- case 'V':
- #endif
- *strout++ = VT;
- break;
-
- default:
- *strout++ = *strptr;
- }
-
- strptr++;
- }
-
- }
- *strout = '\0'; /* Heaven forfend that we forget this! */
- }
-
- prerr(etype, estring)
- int etype;
- char *estring;
- {
- switch(etype)
- {
- case USAGE:
- fprintf(stderr,"%s : Usage: %s [-s] [-d<delimiterstring>] file1 file2 ...\n",cmdnam,cmdnam);
- break;
-
- case NODELIM:
- fprintf(stderr,"%s : no delimiters\n",cmdnam);
- break;
-
- case BADFILE:
- fprintf(stderr,"%s : %s : cannot open\n",cmdnam,estring);
- break;
-
- case TOOMANY:
- fprintf(stderr,"%s : too many files\n",cmdnam);
- break;
- }
- exit(1);
- }